/*
 * Alex Rodrigo de Oliveira - 06/30403
 * Rafaela Porto Osório - 06/94011
 */

#include <time.h>
#include <stdio.h>
#include <sys/msg.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include "msg_handlers.h"
#include "list.h"
#include "mqueue.h"
#include "debug.h"


static void schedule_job(Msg *msg);
static void list_jobs(Msg *msg);
static void remove_job(Msg *msg);
static void finalize(Msg *msg);


/*
 * Estrutura vazia a ser enviada para as funções de tratamentos dos sinais que
 * não precisam desse campo.
 */
static const siginfo_t NOTHING;


/*
 * Variável que guarda o último id que foi atribuído a algum job. Serve para não
 * repetir id's.
 */
static int last_id = 0;


/*
 * Atribui uma função para cada posição válida em handlers.
 * Lembrar de ficar de olho no enum handlers.
 */
void init_msg_handlers()
{
	handlers[MSG_SUBMIT] = schedule_job;
	handlers[MSG_LIST] = list_jobs;
	handlers[MSG_REMOVE] = remove_job;
	handlers[MSG_TERM] = finalize;
}


/*
 * Recebendo job para agendamento.
 * Ajusta o campo id e envia a resposta, calcula o time, insere o job ordenado
 * na lista e ajusta o alarme.
 */
static void schedule_job(Msg *msg)
{
	assert(msg != NULL);
	assert(msg->job.delay > 0);
	assert(msg->job.repeats > 0);
	assert(strlen(msg->job.command) > 0);


	Node *nd;
	int ret;
	
	/* Atribui um id ao job e envia a mensagem de volta para o agendador. */
	msg->job.id = ++last_id;
	ret = msgsnd(mqout, msg, sizeof(Job), 0);
	if (ret == -1) {
		perror("Não foi possível enviar mensagem de resposta");
		exit(1);
	}

	/* Calcula o instante da primeira execução do job. */
	msg->job.time = time(NULL) + msg->job.delay;

	/* Insere o novo job na posição correta da lista. */
	for (nd = schedule.begin; nd != schedule.end; nd = nd->next) {
		if (msg->job.time < nd->data.time) break;
	}
	nd = list_add(&schedule, nd, &msg->job);

	/* Se o novo job é a cabeça da lista, reajusta o alarme. */
	if (nd == schedule.begin) {
		alarm(msg->job.delay);
	}

	debug_print("Novo job agendado. Id = %d, time = %d, list.len = %d\n",
			msg->job.id, (int)msg->job.time, schedule.len);
}


/*
 * Envia de volta uma mensagem para cada job na lista. No final envia uma
 * mensagem toda zerada para funcionar como um '\0'.
 */
static void list_jobs(Msg *msg)
{
	assert(msg != NULL);

	Node *nd;

	for (nd = schedule.begin; nd != schedule.end; nd = nd->next) {
		msg->job = nd->data;
		msgsnd(mqout, msg, sizeof(Job), 0);
	}

	/* Zera todos os campos e envia a última mensagem. */
	memset(&msg->job, 0, sizeof(Job));
	msgsnd(mqout, msg, sizeof(Job), 0);
}


/*
 * Percorre a lista até achar o job desejado e remove ele da lista.
 */
static void remove_job(Msg *msg)
{
	assert(msg != NULL);
	assert(msg->job.id > 0);

	Node *nd;

	for (nd = schedule.begin; nd != schedule.end; nd = nd->next) {
		if (nd->data.id == msg->job.id) {
			list_rem(&schedule, nd);
			break;
		}
	}
}


/*
 * Encerra o programa.
 */
static void finalize(Msg *msg)
{
	exit(0);
}
